from abc import abstractmethod


class HookEnabled(object):
    """A component that facilitates callback mechanism.

    Args:
        pre_hooks: the list of hook functions to be called
            at the entry of ``transform()`` or ``fit()`` method.
        post_hooks: the list of hook functions to be called
            at the exit of ``transform()`` or ``fit()`` method.

    Notes:
        All the hook functions should be unary, that is, accept exactly
        one argument.
    """

    def __init__(self, pre_hooks=(), post_hooks=()):
        self._pre_hooks = []
        self._post_hooks = []

        for p in self._get_default_pre_hooks():
            self.addPreHook(p)
        for p in pre_hooks:
            self.addPreHook(p)

        for p in self._get_default_post_hooks():
            self.addPostHook(p)
        for p in post_hooks:
            self.addPostHook(p)

    def addPreHook(self, hook):
        """Appends a unary function to the pre-hook list.

        Args:
            hook: hook function that accepts one argument

        Returns:
            ``self``
        """
        self._pre_hooks.append(hook)
        return self

    def addPostHook(self, hook):
        """Appends a unary function to the post-hook list.

        Args:
            hook: hook function that accepts one argument

        Returns:
            ``self``
        """
        self._post_hooks.append(hook)
        return self

    def _exec_pre_hooks(self, dataset):
        for hook in self._pre_hooks:
            hook(self, dataset)

    def _exec_post_hooks(self, dataset):
        for hook in self._post_hooks:
            hook(self, dataset)

    def _get_default_pre_hooks(self):
        return []

    def _get_default_post_hooks(self):
        return []


class LoggingEnabled(object):

    def __init__(self):
        self._init_stack()
        self.setInstanceName("undefined")

    @abstractmethod
    def getKernelName(self):
        """
        Returns:
            the name of the kernel

        Notes:
            The return value of this method will appear as a field in the log, and
            be used for tracing object behavior. It helps to locate the user's problem
            when the log is investigated.
            The developer of new ``LoggingEnabled`` object should override this
            method and choose the return value wisely.

            The default return value is the object's class name, which is intended
            when the ``Actuator`` doesn't wrap any exterior kernel, ie, the kernel
            is the object itself.
        """
        return self.__class__.__name__

    @abstractmethod
    def getKernelModule(self):
        """
        Returns:
            the name of the module containing the kernel

        Notes:
            The return value of this method will appear as a field in the log, and
            be used for tracing object behavior. It helps to locate the user's problem
            when the log is investigated.
            The developer of new ``LoggingEnabled`` object should override this
            method and choose the return value wisely.

            The default return value is the module name of the class of the object,
            which is intended when the ``Actuator`` doesn't wrap any exterior kernelm,
            ie, the kernel is the object itself.
        """
        return self.__class__.__module__

    @abstractmethod
    def getExtraInfo(self):
        """

        Returns:
            the content of extra logging information

        Notes:
            Developer of new Actuators may want to override this function.
        """
        return None

    def getInstanceName(self):
        """

        Returns:
            the name of the current instance of ``LoggingEnabled``.

        Notes:
            The instance name is logged. This is potentially useful when the behavior
            of a certain instance is investigated.
        """
        return self._instance_name

    def setInstanceName(self, instance_name):
        """

        Args:
            instance_name: the name of instance

        Returns:
            self

        Notes:
            This name is intended to be used by the end user who want to identify
            a particular instance of Actuator. We suggest that the user give
            a unique name for each distinct instance.

        """
        self._instance_name = instance_name
        return self

    def _init_stack(self):
        self._stack = []

    def _push(self, o):
        self._stack.append(o)

    def _pop(self):
        if len(self._stack) == 0:
            return None
        else:
            ret = self._stack[-1]
            del self._stack[-1]
            return ret
